package com.zhima.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhima.constant.QuestionRedisConstant;
import com.zhima.dto.*;
import com.zhima.entity.*;
import com.zhima.enums.QuestionDifficulty;
import com.zhima.enums.QuestionScope;
import com.zhima.enums.ScoreStrategy;
import com.zhima.exception.ResourceNotFoundException;
import com.zhima.exception.ZhimaOjException;
import com.zhima.feignclient.FileFeignClient;
import com.zhima.feignclient.LocalTaskFeignClient;
import com.zhima.mapper.OjQuestionMapper;
import com.zhima.service.OjQuestionAuditService;
import com.zhima.service.OjQuestionPublishService;
import com.zhima.service.OjQuestionService;
import com.zhima.util.AuthorizationUtil;
import com.zhima.util.PageUtil;
import com.zhima.util.ScoreUtil;
import com.zhima.util.UserHolder;
import com.zhima.vo.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipInputStream;

import static com.zhima.constant.MinioConstant.SPJ_FILE_BUCKET;
import static com.zhima.constant.MinioConstant.TEST_CASES_BUCKET;
import static com.zhima.constant.QuestionRedisConstant.QUESTION_BATCH_INFO_MAP_KEY;
import static com.zhima.constant.QuestionXxlJobConstant.DELETE_QUESTION;
import static com.zhima.constant.TestCaseConstant.*;
import static com.zhima.exception.ErrorCode.*;
import static com.zhima.util.AuthorizationUtil.isLoginUserResource;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zhima
 * @since 2023-08-25
 */
@Service
public class OjQuestionServiceImpl extends ServiceImpl<OjQuestionMapper, OjQuestion> implements OjQuestionService {

    public static final String QUESTION_INFO_PATTERN = "(\\w*)-(.*)【(.*)】";
    public static final String QUESTION_DETAIL_PATTERN = "#(.*)\\s*## 题目背景\\s*(.*)## 题目描述\\s*(.*)## 输入格式\\s*(.*)## 输出格式\\s*(.*)## 样例\\s*(.*)## 提示\\s*(.*)";
    public static final String INPUT_OUTPUT_EXAMPLE = "### 输入 #\\d\\s*```(.*?)```\\s*### 输出 #\\d\\s*```(.*?)```";
    @Autowired
    private OjQuestionAuditService ojQuestionAuditService;

    @Autowired
    private OjQuestionPublishService ojQuestionPublishService;

    @Autowired
    private FileFeignClient fileFeignClient;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private LocalTaskFeignClient localTaskFeignClient;

    @Autowired
    private ExecutorService executorService;

    @Override
    public Page<QuestionVO> pageQueryQuestionBasicInfo(Long userId, Long pageNum, Long pageSize) {
        Page<OjQuestion> pageQueryResult = page(new Page<>(pageNum, pageSize),
                new LambdaQueryWrapper<OjQuestion>()
                        .eq(OjQuestion::getUploaderId, userId)
                        .select(OjQuestion::getId,
                                OjQuestion::getPrefix,
                                OjQuestion::getNumber,
                                OjQuestion::getTitle,
                                OjQuestion::getDifficulty,
                                OjQuestion::getLabels,
                                OjQuestion::getScope)
        );
        return PageUtil.buildPage(pageQueryResult, pageQueryResult.getRecords().stream().map(r -> BeanUtil.copyProperties(r, QuestionVO.class)).collect(Collectors.toList()));
    }

    @Override
    public boolean deleteQuestionById(Long userId, Long questionId) {
        isLoginUserResource(userId, getUploaderId(questionId));
        String questionIdStr = questionId.toString();
        // 删除Redis中的数据
        // 删除ES中的数据
        // 分布式任务
        boolean success = localTaskFeignClient.publishTask(DELETE_QUESTION, List.of(questionIdStr, questionIdStr));
        success &= removeById(questionId);
        success &= ojQuestionAuditService.removeById(questionId);
        success &= ojQuestionPublishService.removeById(questionId);
        return success;
    }

    @Override
    public OjQuestion createNewQuestion(UserDTO userDTO, CreateQuestionDTO createQuestionDTO, String prefix) {
        OjQuestion ojQuestion = BeanUtil.copyProperties(createQuestionDTO, OjQuestion.class);
        // 从Redis读prefix前缀下有几道题目了
        // 可能是新的prefix，所以这里需要做一个判断
        String numberStr = stringRedisTemplate.opsForValue().get(QuestionRedisConstant.QUESTION_PREFIX + prefix);
        int number = Integer.parseInt(numberStr == null ? "0" : numberStr);
        // 题目前缀
        ojQuestion.setPrefix(prefix);
        // 该前缀下的下一个序号
        ojQuestion.setNumber(number + 1);
        // 上传者id
        ojQuestion.setUploaderId(userDTO.getId());
        // 用户头像
        ojQuestion.setUploaderAvatarUri(userDTO.getAvatarUri());
        // 难度暂未评定
        ojQuestion.setDifficulty(createQuestionDTO.getDifficulty());
        // 默认没有标签
        ojQuestion.setLabels(createQuestionDTO.getLabels());
        // 默认私有
        ojQuestion.setScope(createQuestionDTO.getScope());
        // 前缀下的题目数量新增
        stringRedisTemplate.opsForValue().set(QuestionRedisConstant.QUESTION_PREFIX + prefix, String.valueOf(number + 1));
        return save(ojQuestion) ? ojQuestion : null;
    }

    @Override
    public boolean supplyDetail(Long userId, CreateQuestionDTO createQuestionDTO) {
        isLoginUserResource(userId, getUploaderId(createQuestionDTO.getId()));
        OjQuestion ojQuestion = BeanUtil.copyProperties(createQuestionDTO, OjQuestion.class);
        ojQuestion.setUpdateTime(new Date());
        return updateById(ojQuestion);
    }

    @Override
    public QuestionPublishDetailVO queryQuestionDetailById(Long userId, Long questionId) {
        OjQuestion ojQuestion = getById(questionId);
        if (ojQuestion == null) {
            throw new ResourceNotFoundException();
        }
        QuestionPublishDetailVO questionPublishDetailVO = BeanUtil.copyProperties(ojQuestion, QuestionPublishDetailVO.class);
        questionPublishDetailVO.setIsSelf(questionPublishDetailVO.getUploaderId().equals(userId));
        return questionPublishDetailVO;
    }

    @Override
    public List<SimpleQuestionVO> batchQuerySimpleQuestionByIds(List<Long> questionIds) {
        return lambdaQuery()
                .in(OjQuestion::getId, questionIds)
                .select(OjQuestion::getId, OjQuestion::getPrefix, OjQuestion::getNumber, OjQuestion::getTitle, OjQuestion::getDifficulty)
                .list()
                .stream().map(r -> BeanUtil.copyProperties(r, SimpleQuestionVO.class))
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateQuestionScope(Long userId, Long questionId, QuestionScope questionScope) {
        isLoginUserResource(userId, getUploaderId(questionId));
        // 三张表直接更新可见性
        return lambdaUpdate().eq(OjQuestion::getId, questionId).set(OjQuestion::getScope, questionScope).update()
                && ojQuestionAuditService.lambdaUpdate().eq(OjQuestionAudit::getId, questionId).set(OjQuestionAudit::getScope, questionScope).update()
                && ojQuestionPublishService.lambdaUpdate().eq(OjQuestionPublish::getId, questionId).set(OjQuestionPublish::getScope, questionScope).update();
    }

    @Override
    public boolean updateTestCases(Long userId, InputStream inputStream, Long questionId) {
        isLoginUserResource(userId, getUploaderId(questionId));
        // 创建临时文件夹
        String tmpDirPath = FileUtil.getTmpDirPath();
        File tempDir = new File(String.format("%s%s", tmpDirPath, UUID.fastUUID()));
        if (!tempDir.mkdir()) {
            throw new ZhimaOjException(CREATE_DIR_FAILURE);
        }
        // 解压缩文件
        ZipUtil.unzip(new ZipInputStream(inputStream), tempDir);
        File[] testCaseFiles = tempDir.listFiles();
        // 检查测试用例文件是否成对出现
        if (testCaseFiles == null || testCaseFiles.length % 2 != 0) {
            FileUtil.del(tempDir.getAbsolutePath());
            throw new ZhimaOjException(TEST_CASES_NUMBER_IS_ODD);
        }
        for (int i = 0; i < testCaseFiles.length; i += 2) {
            String inFileName = testCaseFiles[i].getName();
            String outFileName = testCaseFiles[i + 1].getName();
            String inFileNameWithoutSuffix = inFileName.substring(0, inFileName.lastIndexOf('.'));
            String outFileNameWithoutSuffix = outFileName.substring(0, outFileName.lastIndexOf('.'));
            if (!inFileNameWithoutSuffix.equals(outFileNameWithoutSuffix)) {
                FileUtil.del(tempDir.getAbsolutePath());
                throw new ZhimaOjException(TEST_CASES_NUMBER_IS_ODD);
            }
        }
        // 统计测试用例数量
        List<TestCaseTemplate> testCaseTemplates = TestCaseTemplate.initNTestCaseTemplates(testCaseFiles.length / 2);
        File ansTempDir = new File(String.format("%s%s", tmpDirPath, UUID.fastUUID().toString()));
        if (!ansTempDir.mkdir()) {
            FileUtil.del(tempDir.getAbsolutePath());
            throw new ZhimaOjException(CREATE_DIR_FAILURE);
        }
        // 按照顺序重命名文件
        for (int i = 0; i < testCaseFiles.length; i += 2) {
            File testCaseInFile = testCaseFiles[i];
            if (!testCaseInFile.getName().endsWith(IN)) {
                FileUtil.del(tempDir.getAbsolutePath());
                throw new ZhimaOjException(TEST_CASE_FILE_MUST_IN);
            }
            File testCaseOutFile = testCaseFiles[i + 1];
            if (!testCaseOutFile.getName().endsWith(OUT)) {
                FileUtil.del(tempDir.getAbsolutePath());
                throw new ZhimaOjException(TEST_CASE_FILE_MUST_OUT);
            }
            FileUtil.copy(testCaseInFile, new File(String.format("%s%stest%d.%s", ansTempDir.getAbsolutePath(), File.separator, i / 2, IN)), true);
            FileUtil.copy(testCaseOutFile, new File(String.format("%s%stest%d.%s", ansTempDir.getAbsolutePath(), File.separator, i / 2, OUT)), true);
        }
        // 重新压缩文件
        File zipTestCaseFile = ZipUtil.zip(ansTempDir);
        // 上传新的压缩文件到minio
        FeignUploadResultDTO uploadResultDTO = fileFeignClient.upload(userId, new FeignUploadFileDTO(ZIP_TEST_CASE_FILE_NAME, FileUtil.readBytes(zipTestCaseFile)), TEST_CASES_BUCKET);
        FileUtil.del(tempDir.getAbsolutePath());
        FileUtil.del(ansTempDir.getAbsolutePath());
        return lambdaUpdate().eq(OjQuestion::getId, questionId)
                // 默认总计分策略是加和
                .set(OjQuestion::getScoreStrategy, ScoreStrategy.SUM)
                .set(OjQuestion::getMaxMemoryLimit, DEFAULT_MAX_MEMORY_LIMIT)
                .set(OjQuestion::getMaxTimeLimit, DEFAULT_MAX_TIME_LIMIT)
                .set(OjQuestion::getTestCaseFileId, uploadResultDTO.getFileId())
                .set(OjQuestion::getTestCaseFileSize, uploadResultDTO.getFileSize())
                .set(OjQuestion::getFullMark, ScoreUtil.FULL_MARK)
                .set(OjQuestion::getTestCaseTemplates, JSON.toJSONString(testCaseTemplates)).update();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public BatchImportQuestionInfo[] batchImportQuestions(InputStream inputStream) {
        UserDTO userDTO = UserHolder.get();
        String tmpDirPath = FileUtil.getTmpDirPath();
        File tempDir = new File(String.format("%s%s", tmpDirPath, UUID.fastUUID()));
        if (!tempDir.mkdir()) {
            throw new ZhimaOjException(CREATE_DIR_FAILURE);
        }
        // 解压
        ZipUtil.unzip(new ZipInputStream(inputStream, Charset.forName("GBK")), tempDir);
        File[] questionDirFiles;
        if (!tempDir.isDirectory() || (questionDirFiles = tempDir.listFiles()) == null) {
            throw new ZhimaOjException(String.format("%s ERROR:%s", BATCH_IMPORT_FILE_FORMAT_ERROR.getErrorMessage(), "压缩包解压后不是文件夹或文件夹为空"));
        }
        BatchImportQuestionInfo[] batchImportQuestionInfos = new BatchImportQuestionInfo[questionDirFiles.length];
        Map<String, String> batchImportQuestionInfoMap = new HashMap<>();
        for (int i = 0; i < questionDirFiles.length; i++) {
            File questionDirFile = questionDirFiles[i];
            batchImportQuestionInfos[i] = new BatchImportQuestionInfo(questionDirFile.getName(), "");
            batchImportQuestionInfoMap.put(batchImportQuestionInfos[i].getQuestionName(), "");
            // 每一个题目都是一个文件夹
            if (!questionDirFile.isDirectory()) {
                throw new ZhimaOjException(String.format("%s ERROR:%s", BATCH_IMPORT_FILE_FORMAT_ERROR.getErrorMessage(), "每一个题目都是一个文件夹"));
            }
        }
        // 将状态和信息保存在redis中
        // 题目名称、错误信息
        // 在Redis中的就是在导入中的、或者出现错误的
        String questionBatchInfoMapKey = QUESTION_BATCH_INFO_MAP_KEY + userDTO.getId();
        stringRedisTemplate.opsForHash().putAll(questionBatchInfoMapKey, batchImportQuestionInfoMap);
        // 放入本服务器的线程池中执行
        executorService.execute(() -> {
            for (File questionDirFile : questionDirFiles) {
                String questionDirName = questionDirFile.getName();
                Pattern pattern = Pattern.compile(QUESTION_INFO_PATTERN);
                Matcher matcher = pattern.matcher(questionDirName);
                boolean find = matcher.find();
                if (!find) {
                    stringRedisTemplate.opsForHash().put(questionBatchInfoMapKey, questionDirName, String.format("%s ERROR:%s", BATCH_IMPORT_FILE_FORMAT_ERROR.getErrorMessage(), "题目文件夹命名格式不规范"));
                    continue;
                }
                String title = matcher.group(2);
                String prefix = matcher.group(1);
                File[] questionInfoDirFiles = questionDirFile.listFiles();
                if (questionInfoDirFiles == null || questionInfoDirFiles.length == 0) {
                    // 文件详情questionDirFile文件夹里面没有文件
                    stringRedisTemplate.opsForHash().put(questionBatchInfoMapKey, questionDirName, String.format("%s ERROR:%s", BATCH_IMPORT_FILE_FORMAT_ERROR.getErrorMessage(), "题目文件夹里面没有文件"));
                    continue;
                }
                CreateQuestionDTO createQuestionDTO = new CreateQuestionDTO();
                createQuestionDTO.setTitle(title);
                createQuestionDTO.setDifficulty(QuestionDifficulty.valueOf(matcher.group(3).toUpperCase(Locale.ROOT)));
                // 默认私有
                createQuestionDTO.setScope(QuestionScope.PRIVATE);
                createQuestionDTO.setLabels(Collections.emptyList());
                // 填充内容
                boolean isSuccess = supplyDetail(questionBatchInfoMapKey, createQuestionDTO, questionInfoDirFiles[0], questionDirName);
                // 填充题目信息失败
                if (!isSuccess) {
                    continue;
                }
                OjQuestion ojQuestion = createNewQuestion(userDTO, createQuestionDTO, prefix);
                // 题目创建失败
                if (ojQuestion == null) {
                    stringRedisTemplate.opsForHash().put(questionBatchInfoMapKey, questionDirName, String.format("%s ERROR:%s", BATCH_IMPORT_FILE_FORMAT_ERROR.getErrorMessage(), "题目创建失败"));
                    continue;
                }
                // 题目创建成功并且没有配置测试用例则直接返回
                if (questionInfoDirFiles.length == 1) {
                    stringRedisTemplate.opsForHash().delete(questionBatchInfoMapKey, questionDirName);
                    continue;
                }
                // 下载测试用例
                String line = FileUtil.readString(questionInfoDirFiles[1], StandardCharsets.UTF_8);
                if (StrUtil.isEmpty(line) || !line.startsWith("http://")) {
                    stringRedisTemplate.opsForHash().put(questionBatchInfoMapKey, questionDirName, String.format("%s ERROR:%s", BATCH_IMPORT_FILE_FORMAT_ERROR.getErrorMessage(), "测试文件地址错误"));
                    continue;
                }
                // 下载并解析测试用例文件，直接调之前写好的上传测试用例文件的逻辑
                try {
                    ResponseEntity<byte[]> responseEntity = restTemplate.exchange(line, HttpMethod.GET, null, byte[].class);
                    if (responseEntity.getBody() == null) {
                        stringRedisTemplate.opsForHash().put(questionBatchInfoMapKey, questionDirName, String.format("%s ERROR:%s", BATCH_IMPORT_FILE_FORMAT_ERROR.getErrorMessage(), "测试用例下载失败"));
                        continue;
                    }
                    updateTestCases(
                            userDTO.getId(),
                            new ByteArrayInputStream(responseEntity.getBody()),
                            ojQuestion.getId()
                    );
                    stringRedisTemplate.opsForHash().delete(questionBatchInfoMapKey, questionDirName);
                } catch (Exception e) {
                    stringRedisTemplate.opsForHash().put(questionBatchInfoMapKey, questionDirName, String.format("%s ERROR:%s", BATCH_IMPORT_FILE_FORMAT_ERROR.getErrorMessage(), "测试用例下载失败"));
                }
            }
            FileUtil.del(tempDir);
        });
        // 返回题目状态的数组
        return batchImportQuestionInfos;
    }

    @Override
    public QuestionEditVO queryQuestionEditById(Long userId, Long questionId) {
        OjQuestion ojQuestion = getById(questionId);
        // 判空
        if (ojQuestion == null) {
            throw new ResourceNotFoundException();
        }
        AuthorizationUtil.isLoginUserResource(userId, ojQuestion.getUploaderId());
        return BeanUtil.copyProperties(ojQuestion, QuestionEditVO.class);
    }

    @Override
    public TestCaseTemplateVO getTestCasesTemplate(Long questionId) {
        OjQuestion one = lambdaQuery().eq(OjQuestion::getId, questionId).select(OjQuestion::getTestCaseTemplates, OjQuestion::getTestCaseFileSize, OjQuestion::getScoreStrategy).one();
        TestCaseTemplateVO testCaseTemplateVO = new TestCaseTemplateVO();
        testCaseTemplateVO.setSubTaskIndexMap(one.getTestCaseTemplates().stream().collect(Collectors.groupingBy(TestCaseTemplate::getSubTaskIndex)));
        testCaseTemplateVO.setTestCaseFileSize(one.getTestCaseFileSize());
        testCaseTemplateVO.setScoreStrategy(one.getScoreStrategy());
        testCaseTemplateVO.setSpjFileId(one.getSpjFileId());
        return testCaseTemplateVO;
    }

    @Override
    public boolean updateTestCasesTemplate(Long questionId, UpdateTestCaseTemplateDTO updateTestCaseTemplateDTO) {
        // 删除原本所有的spjFile
        OjQuestion one = lambdaQuery().eq(OjQuestion::getId, questionId).select(OjQuestion::getTestCaseTemplates, OjQuestion::getSpjFileId).one();
        if (one.getSpjFileId() != null) {
            fileFeignClient.deleteFileByFileId(one.getSpjFileId());
        }
        HashSet<Long> spjFileIdSet = new HashSet<>();
        for (TestCaseTemplate testCaseTemplate : one.getTestCaseTemplates()) {
            if (testCaseTemplate.getSpjFileId() != null && !spjFileIdSet.contains(testCaseTemplate.getSpjFileId())) {
                fileFeignClient.deleteFileByFileId(testCaseTemplate.getSpjFileId());
                spjFileIdSet.add(testCaseTemplate.getSpjFileId());
            }
        }
        Map<Integer, ScoreStrategy> subTaskScoreStrategy = updateTestCaseTemplateDTO.getSubTaskScoreStrategyMap();
        List<TestCaseTemplate> testCaseTemplates = updateTestCaseTemplateDTO.getTestCaseTemplates();
        // 更新计分策略
        // 前端并没有更新每个测试模板的计分策略
        for (TestCaseTemplate testCaseTemplate : testCaseTemplates) {
            ScoreStrategy scoreStrategy = subTaskScoreStrategy.get(testCaseTemplate.getSubTaskIndex());
            testCaseTemplate.setScoreStrategy(scoreStrategy);
            if (scoreStrategy == ScoreStrategy.SPJ) {
                // 置空
                testCaseTemplate.setSpjFileId(null);
            }
        }
        LambdaUpdateChainWrapper<OjQuestion> lambdaUpdateChainWrapper = lambdaUpdate().eq(OjQuestion::getId, questionId)
                .set(OjQuestion::getScoreStrategy, updateTestCaseTemplateDTO.getScoreStrategy())
                .set(OjQuestion::getMaxMemoryLimit, updateTestCaseTemplateDTO.getMaxMemoryLimit())
                .set(OjQuestion::getMaxTimeLimit, updateTestCaseTemplateDTO.getMaxTimeLimit());
        Map<Integer, String> spjContentMap = updateTestCaseTemplateDTO.getSpjContentMap();
        // 处理SPJ测评方式
        if (!spjContentMap.isEmpty()) {
            Set<Integer> keySet = spjContentMap.keySet();
            for (Integer subTaskIndex : keySet) {
                // 文件名就是题目id+subTaskIndex
                String spjContent = spjContentMap.get(subTaskIndex);
                if (StrUtil.isEmpty(spjContent)) {
                    continue;
                }
                FeignUploadResultDTO uploadResult = fileFeignClient.upload(
                        one.getUploaderId(),
                        new FeignUploadFileDTO(String.format("%s%d.txt", questionId, subTaskIndex), spjContent.getBytes(StandardCharsets.UTF_8)),
                        SPJ_FILE_BUCKET
                );
                // 总分计分策略
                if (subTaskIndex == -1) {
                    lambdaUpdateChainWrapper.set(OjQuestion::getSpjFileId, uploadResult.getFileId());
                } else {
                    testCaseTemplates.forEach(t -> {
                        if (t.getSubTaskIndex().equals(subTaskIndex)) {
                            t.setSpjFileId(uploadResult.getFileId());
                        }
                    });
                }
            }
        }
        lambdaUpdateChainWrapper.set(OjQuestion::getTestCaseTemplates, JSON.toJSONString(testCaseTemplates));
        int fullMark = 0;
        Map<Integer, List<TestCaseTemplate>> subTaskIndexMap = testCaseTemplates.stream().collect(Collectors.groupingBy(TestCaseTemplate::getSubTaskIndex));
        for (Map.Entry<Integer, List<TestCaseTemplate>> entry : subTaskIndexMap.entrySet()) {
            ScoreStrategy scoreStrategy = entry.getValue().get(0).getScoreStrategy();
            fullMark += scoreStrategy.calculation(entry.getValue().stream().map(TestCaseTemplate::getScore).collect(Collectors.toList()));
        }
        // 设置总分
        lambdaUpdateChainWrapper.set(OjQuestion::getFullMark, fullMark);
        return lambdaUpdateChainWrapper.update();
    }

    @Override
    public byte[] downloadTestCase(Long questionId) {
        return fileFeignClient.downloadByFileId(lambdaQuery().eq(OjQuestion::getId, questionId).select(OjQuestion::getTestCaseFileId).one().getTestCaseFileId()).getBytes();
    }

    @Override
    public BatchImportQuestionInfo[] batchImportQuestionInfos(Long userId) {
        Map<Object, Object> questionInfos = stringRedisTemplate.opsForHash().entries(QUESTION_BATCH_INFO_MAP_KEY + userId);
        BatchImportQuestionInfo[] batchImportQuestionInfos = new BatchImportQuestionInfo[questionInfos.size()];
        int index = 0;
        for (Map.Entry<Object, Object> entry : questionInfos.entrySet()) {
            batchImportQuestionInfos[index] = new BatchImportQuestionInfo(entry.getKey().toString(), entry.getValue().toString());
            index++;
        }
        return batchImportQuestionInfos;
    }

    @Override
    public boolean deleteBatchQuestionInfo(Long userId, String questionName) {
        Long delete = stringRedisTemplate.opsForHash().delete(QUESTION_BATCH_INFO_MAP_KEY + userId, questionName);
        return delete > 0;
    }

    private boolean supplyDetail(String questionBatchInfoMapKey, CreateQuestionDTO createQuestionDTO, File questionInfoFile, String questionDirName) {
        // 解析文件
        Pattern pattern = Pattern.compile(QUESTION_DETAIL_PATTERN, Pattern.MULTILINE | Pattern.DOTALL);
        Matcher matchResult = pattern.matcher(FileUtil.readString(questionInfoFile, StandardCharsets.UTF_8));
        if (!matchResult.find()) {
            // 文件格式错误
            stringRedisTemplate.opsForHash().put(questionBatchInfoMapKey, questionDirName, String.format("%s ERROR:%s", BATCH_IMPORT_FILE_FORMAT_ERROR, "题目文件不符合格式"));
            return false;
        }
        createQuestionDTO.setBackground(matchResult.group(2));
        createQuestionDTO.setDescription(matchResult.group(3));
        createQuestionDTO.setInputFormat(matchResult.group(4));
        createQuestionDTO.setOutputFormat(matchResult.group(5));
        createQuestionDTO.setTips(matchResult.group(4));
        String inputOutExampleGroupsString = matchResult.group(6);
        pattern = Pattern.compile(INPUT_OUTPUT_EXAMPLE, Pattern.MULTILINE | Pattern.DOTALL);
        matchResult = pattern.matcher(inputOutExampleGroupsString);
        if (!matchResult.find()) {
            stringRedisTemplate.opsForHash().put(questionBatchInfoMapKey, questionDirName, String.format("%s ERROR:%s", BATCH_IMPORT_FILE_FORMAT_ERROR, "文件输入输出部分格式出错"));
            return false;
        }
        List<String> inputExamples = CharSequenceUtil.splitTrim(matchResult.group(1), "\n\n");
        List<String> outputExamples = CharSequenceUtil.splitTrim(matchResult.group(2), "\n\n");
        List<InOutExample> inOutExamples = new ArrayList<>();
        for (int i = 0; i < inputExamples.size(); i++) {
            InOutExample inOutExample = new InOutExample();
            inOutExample.setInputExample(inputExamples.get(i).replaceAll("\r\n", "\n"));
            inOutExample.setOutputExample(outputExamples.get(i).replaceAll("\r\n", "\n"));
            inOutExamples.add(inOutExample);
        }
        createQuestionDTO.setInOutExamples(inOutExamples);
        return true;
    }


    private Long getUploaderId(Long questionId) {
        return lambdaQuery().eq(OjQuestion::getId, questionId).select(OjQuestion::getUploaderId).one().getUploaderId();
    }
}
